package com.zsc.oj.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;

import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.zsc.oj.common.ErrorCode;
import com.zsc.oj.constant.FileConstant;
import com.zsc.oj.exception.BusinessException;
import com.zsc.oj.manager.CosManager;
import com.zsc.oj.mapper.TeamMapper;
import com.zsc.oj.model.dto.team.*;
import com.zsc.oj.model.entity.Team;
import com.zsc.oj.model.entity.User;
import com.zsc.oj.model.entity.UserTeam;
import com.zsc.oj.model.enums.FileUploadBizEnum;
import com.zsc.oj.model.enums.TeamStatusEnums;
import com.zsc.oj.model.enums.UserRoleEnum;
import com.zsc.oj.model.vo.UserVO;
import com.zsc.oj.service.TeamService;
import com.zsc.oj.service.UserService;
import com.zsc.oj.service.UserTeamService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import java.io.File;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeUnit;


@Service
@Slf4j
@Transactional(rollbackFor = Exception.class)//开启事务,事务回滚抛异常
public class TeamServiceImpl extends ServiceImpl<TeamMapper, Team>
        implements TeamService {

    @Resource
    private UserTeamService userTeamService;

    @Resource
    private UserService userService;

    @Resource
    private RedissonClient redissonClient;

    @Resource
    private CosManager cosManager;

    /**
     * 创建队伍
     * @param team：所要创建的队列
     * @param loginUser：当前的登录用户
     * @return
     */
    @Override
    public long createTeam(Team team, User loginUser) {
        //1. 请求参数是否为空？
        if (team == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        //2. 是否登录，未登录不允许创建
        if (loginUser == null) {
            throw new BusinessException(ErrorCode.NOT_LOGIN_ERROR);
        }
        long userId = loginUser.getId();
        //3. 校验信息
        //   1. 队伍人数 > 1 且 <= 20
        int teamNum = Optional.ofNullable(team.getMaxNum()).orElse(0);
        if (teamNum < 1 || teamNum > 20) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "队伍人数错误");
        }
        //   2. 队伍标题 <= 20
        String teamName = team.getName();
        if (StringUtils.isBlank(teamName) || teamName.length() > 20) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "队伍标题错误");
        }
        //   3. 描述 <= 512
        String description = team.getDescription();
        if (StringUtils.isNotBlank(description) && description.length() > 512) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "队伍描述错误");
        }
        //获取当前队伍的状态
        int status = Optional.ofNullable(team.getStatus()).orElse(0);
        TeamStatusEnums teamStatusEnums = TeamStatusEnums.getEnumByNum(status);
        //   4. status 是否公开（int）不传默认为 0（公开）
        if (teamStatusEnums == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "队伍状态错误");
        }
        //   5. 如果 status 是加密状态，一定要有密码，且密码 <= 32
        String password = team.getPassword();
        if (TeamStatusEnums.SECRET.equals(teamStatusEnums)) {//如果 status 是加密状态
            if (StringUtils.isBlank(password) || password.length() > 32) {
                throw new BusinessException(ErrorCode.PARAMS_ERROR, "密码设置错误");
            }
        }
        //   6. 超时时间 > 当前时间
        Date expireTime = team.getExpireTime();
        if (new Date().after(expireTime)) {//当前时间在过期时间之后，那就异常
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "超时时间异常");
        }

        //   7. 校验用户最多创建 5 个队伍
        // todo 有bug，用户可以创建100个队伍【需要使用锁】
        QueryWrapper<Team> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("userId", userId);//判断当前队伍是由当前用户创建的
        long count = this.count(queryWrapper);
        if (count >= 5) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户最多创建5个队伍");
        }
        //4. 插入队伍信息到队伍表
        team.setId(null);
        team.setUserId(userId);
        boolean save = this.save(team);
        if (!save) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "队伍插入不成功");
        }
        //5. 插入用户  => 队伍关系到关系表
        UserTeam userTeam = new UserTeam();
        userTeam.setUserId(userId);
        userTeam.setTeamId(team.getId());
        userTeam.setJoinTime(new Date());

        boolean result = userTeamService.save(userTeam);
        if (!result) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户队伍关系表插入不成功");
        }
        return team.getId(); //这个team的id是新插入后team的id
    }

    /**
     * 查询队伍
     *
     * @param teamQueryRequest
     * @param isAdmin
     * @return
     */
    @Override
    public List<TeamUserVO> listTeam(TeamQueryRequest teamQueryRequest, boolean isAdmin) {
        QueryWrapper<Team> queryWrapper = new QueryWrapper<>();
        /*1、从请求参数中取出队伍名称等查询条件，如果存在则作为查询条件*/
        if (teamQueryRequest != null) {

            /*3、可以通过某个关键词同时对名称和描述查询*/
            String searchText = teamQueryRequest.getSearchText();
            if (StringUtils.isNotBlank(searchText)) {
                queryWrapper.and(qw -> qw.like("name", searchText).or().like("description", searchText));
            }

            //根据队伍id查询出队伍
            Long id = teamQueryRequest.getId();
            if (id != null) {
                queryWrapper.eq("id", id);
            }

            //根据idList查询出当前用户所加入的队伍
            List<Long> idList = teamQueryRequest.getIdList();
            if(CollectionUtils.isNotEmpty(idList)){
                queryWrapper.in("id",idList);
            }

            //根据队伍名称查询队伍
            String name = teamQueryRequest.getName();
            if (StringUtils.isNotBlank(name)) {
                queryWrapper.like("name", name);
            }

            //根据队伍的描述查询出队伍
            String description = teamQueryRequest.getDescription();
            if (StringUtils.isNotBlank(description)) {
                queryWrapper.like("description", description);
            }

            //查询最大人数相等的
            Integer maxNum = teamQueryRequest.getMaxNum();
            if (maxNum != null && maxNum > 0) {
                queryWrapper.eq("maxNum", maxNum);
            }

            //根据创建人查询
            Long userId = teamQueryRequest.getUserId();
            if (userId != null && userId > 0) {
                queryWrapper.eq("userId", userId);
            }

            //根据队伍的状态查询出队伍
            Integer status = teamQueryRequest.getStatus();

            /*4、只有管理员才能查看加密还有非公开的房间*/
            TeamStatusEnums statusEnums = TeamStatusEnums.getEnumByNum(status);

            //如果队伍的状态为空则设置为公开的
            if (statusEnums == null) {
                statusEnums = TeamStatusEnums.PUBLIC;
            }
            //当前登录用户不是队伍的创建人并且status等于私密并且不是管理员，就异常
//            if (!statusEnums.equals(TeamStatusEnums.PUBLIC) && !isAdmin) {
//                throw new BusinessException(ErrorType.NO_AUTH);
//            }
            queryWrapper.eq("status", statusEnums.getNum());

        }

        /*2、不展示已过期的队伍（根据过期时间筛选）*/
        //使用queryWrapper过滤器，满足过期时间不为空和过期时间大于当前时间的数据才能查出来
        //这里使用的是lambda表达式，因为每个queryWrapper都是通过and连接的，所以这里用queryWrapper连接一个查询表达式
        queryWrapper.and(qw -> qw.isNull("expireTime").or().gt("expireTime", new Date()));


        List<Team> teamList = this.list(queryWrapper);
        if (teamList.isEmpty()) {
            return new ArrayList<>();
        }

        ArrayList<TeamUserVO> teamUserVOList = new ArrayList<>();

        //关联查询创建人的用户信息
        for (Team team : teamList) {
            Long userId = team.getUserId();
            if (userId == null) {
                continue;
            }

            //将查询到的队伍进行封装再传给前端（主要将一些敏感字段不输出给前端）
            TeamUserVO teamUserVO = new TeamUserVO();
            try {
                BeanUtils.copyProperties(team,teamUserVO);
            } catch (Exception e) {
                e.printStackTrace();
            }

            /*5、获取到创建人的用户信息*/
            //并且对用户信息进行封装（将密码等一些敏感信息不输出给前端）
            User user = userService.getById(userId);
            if (user != null) {
                UserVO userVO = new UserVO();
                try {
                    BeanUtils.copyProperties(user,userVO);
                } catch (Exception e) {
                    e.printStackTrace();
                }

                //将队伍创建者的信息通过teamUser封装类传给前端
                teamUserVO.setCreateUser(userVO);
            }

            //将封装好的每一个结果都传入到这个数组中返回给前端
            teamUserVOList.add(teamUserVO);

        }

        return teamUserVOList;
    }

    /**
     * 更新队伍
     *
     * @param teamUpdateRequest
     * @param loginUser
     * @return
     */
    @Override
    public boolean updateTeam(TeamUpdateRequest teamUpdateRequest, User loginUser) {
        if (teamUpdateRequest == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        Long id = teamUpdateRequest.getId();
        if (id == null || id < 0) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }

        //根据id查询队伍，并校验队伍是否存在
        Team oldTeam = this.getById(id);
        if (oldTeam == null) {
            throw new BusinessException(ErrorCode.NOT_FOUND_ERROR, "队伍不存在");
        }
        if (loginUser.getId() != oldTeam.getUserId() && userService.isAdmin(loginUser)) {
            throw new BusinessException(ErrorCode.NO_AUTH_ERROR);
        }

        TeamStatusEnums statusEnums = TeamStatusEnums.getEnumByNum(teamUpdateRequest.getStatus());

        if (statusEnums == TeamStatusEnums.SECRET) {
            if (StringUtils.isBlank(oldTeam.getPassword()) && StringUtils.isBlank(teamUpdateRequest.getPassword())) {
                throw new BusinessException(ErrorCode.PARAMS_ERROR, "加密队伍需要设置密码");
            }
        }

        Team updateTeam = new Team();
        try {
            //将teamUpdateRequest（需要修改的信息）传给updateTeam，因为下面的this.updateById只接收updateTeam类型的参数
            BeanUtils.copyProperties(teamUpdateRequest,updateTeam);
        } catch (Exception e) {
            e.printStackTrace();
        }
        boolean result = this.updateById(updateTeam);

        return result;
    }

    /**
     * 用户加入队伍
     *
     * @param teamJoinRequest
     * @param loginUser
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean joinTeam(TeamJoinRequest teamJoinRequest, User loginUser) {
        if (teamJoinRequest == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }

        long userId = loginUser.getId();



        /*2. 队伍必须存在，用户不能加入已满的队伍或已过期的队伍*/
        Long teamId = teamJoinRequest.getTeamId();
        if (teamId == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        Team team = this.getById(teamId);
        if (team == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "队伍不存在");
        }
        if (team.getExpireTime() != null && team.getExpireTime().before(new Date())) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "队伍已过期");
        }

        /*4. 禁止加入私有的队伍*/
        TeamStatusEnums statusEnums = TeamStatusEnums.getEnumByNum(team.getStatus());
        if (TeamStatusEnums.PRIVATE.equals(statusEnums)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "不能加入私有的队伍");
        }

        /*5. 如果队伍是加密的，必须密码匹配才可加入*/
        if (TeamStatusEnums.SECRET.equals(statusEnums)) {
            if (StringUtils.isBlank(teamJoinRequest.getPassword()) || !teamJoinRequest.getPassword().equals(team.getPassword())) {
                throw new BusinessException(ErrorCode.PARAMS_ERROR, "密码错误");
            }
        }

        /*3. 不能加入自己的队伍，不能重复加入已加入的队伍*/
        // todo 用户在一秒内疯狂点击仍能加入重复的队伍，需要用到锁

        RLock lock = redissonClient.getLock("partner_match:join_team");
        try {
            while(true){
                if (lock.tryLock(0, 30000L, TimeUnit.MILLISECONDS)) {//第一个参数为等待时间，第二参数为过期时间
                    QueryWrapper<UserTeam> queryWrapper = new QueryWrapper();
                    queryWrapper.eq("teamId", teamId);
                    queryWrapper.eq("userId", userId);
                    long haveUserJoinTeam = userTeamService.count(queryWrapper);
                    if (haveUserJoinTeam > 0) {
                        throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户已加入队伍");
                    }

                    /*1. 用户最多加入5个队伍*/
                    queryWrapper.eq("userId", userId);
                    long count = userTeamService.count(queryWrapper);
                    if (count > 5) {
                        throw new BusinessException(ErrorCode.PARAMS_ERROR, "一个人最多加入5个队伍");
                    }

                    queryWrapper = new QueryWrapper();
                    queryWrapper.eq("teamId", teamId);
                    long teamHashJoinNum = userTeamService.count(queryWrapper); //统计关联表的teamId的数量就能知道有多少人加入队伍
                    if (teamHashJoinNum > 5) {
                        throw new BusinessException(ErrorCode.PARAMS_ERROR, "队伍已满");
                    }

                    /*6、新增 队伍-用户 关联关系*/
                    UserTeam userTeam = new UserTeam();
                    userTeam.setUserId(userId);
                    userTeam.setTeamId(teamId);
                    userTeam.setJoinTime(new Date());

                    boolean result = userTeamService.save(userTeam);

                    return result;
                }
            }

        } catch (InterruptedException e) {
            log.error("joinTeam error");
            return false;
        } finally {
            if (lock.isHeldByCurrentThread()) {
                System.out.println("unLock: " + Thread.currentThread().getId());
                lock.unlock();
            }
        }
    }

    /**
     * 用户退出队伍
     *
     * @param teamQuitRequest
     * @param loginUser
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean quitTeam(TeamQuitRequest teamQuitRequest, User loginUser) {
        if (teamQuitRequest == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        /*1、校验队伍是否存在*/
        Long teamId = teamQuitRequest.getTeamId();//前端传过来的所要删除队伍的id
        Team team = this.getTeamById(teamId);

        /*2、校验用户是否已经加入队伍*/
        long userId = loginUser.getId();
        QueryWrapper<UserTeam> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("userId", userId);
        queryWrapper.eq("teamId", teamId);
        long count = userTeamService.count(queryWrapper);
        if (count == 0) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户未加入队伍");
        }

        /*3、查看队伍的人数*/
        QueryWrapper userTeamQueryWrapper = new QueryWrapper();
        userTeamQueryWrapper.eq("teamId", teamId);
        long teamHashJoinNum = userTeamService.count(userTeamQueryWrapper); //统计关联表的teamId的数量就能知道有多少人加入队伍
        /*3、只剩一个人，队伍解散*/
        if (teamHashJoinNum == 1) {
            this.removeById(teamId);
            return userTeamService.remove(userTeamQueryWrapper);//删除关联表中teamId = 所要删除队伍的id的数据
        } else {
            /*3、不止一个人的话：如果队长退出队伍，权限转移给第二早加入的用户 —— 先来后到*/
            if (team.getUserId() == userId) { //是队长，并且队长要退出队伍
                userTeamQueryWrapper = new QueryWrapper();
                //如何判断第二早加入的用户？
                //这里根据关联表中teamId相同的id的大小来判断，teamId相等代表加入的是同一个队伍
                //最先加入的id值大，所以这里我们将其进行从小到大进行排序并取出前两条数据，第一条为队长，第二条即为所查
                userTeamQueryWrapper.eq("teamId", teamId);
                userTeamQueryWrapper.last("order by id asc limit 2");
                List<UserTeam> list = userTeamService.list(userTeamQueryWrapper);
                if (CollectionUtils.isEmpty(list) || list.size() <= 1) {
                    throw new BusinessException(ErrorCode.SYSTEM_ERROR);
                }

                //第二早加入的用户
                UserTeam nextUser = list.get(1);
                Long nextUserUserId = nextUser.getUserId();

                //更新当前队伍的队长
                Team updateTeam = new Team();
                updateTeam.setId(teamId);
                updateTeam.setUserId(nextUserUserId);
                boolean result = this.updateById(updateTeam);
                if (!result) {
                    throw new BusinessException(ErrorCode.SYSTEM_ERROR, "更新队长失败");
                }

                //
                //删除掉关联表中退出的用户（队长）的这条队伍的信息
                return userTeamService.remove(queryWrapper);
            } else { //不是队长，直接删除关联表的这条用户的信息
                return userTeamService.remove(queryWrapper);
            }
        }
    }

    /**
     * 队长删除队伍
     *
     * @param teamQuitRequest
     * @param loginUser
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean leaderQuitTeam(TeamQuitRequest teamQuitRequest, User loginUser) {
        if (teamQuitRequest == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }

        long userId = loginUser.getId();
        Long teamId = teamQuitRequest.getTeamId();
        Team team = this.getTeamById(teamId);

        if (userId != team.getUserId() && !loginUser.getUserRole().equals(UserRoleEnum.ADMIN.getValue())) {//不是队长或管理员
            throw new BusinessException(ErrorCode.NO_AUTH_ERROR, "不是队长或管理员，无权限");
        }

        //删除关系表中对应的队伍
        QueryWrapper queryWrapper = new QueryWrapper();
        queryWrapper.eq("teamId", teamId);
        boolean remove = userTeamService.remove(queryWrapper);
        if (!remove) {
            throw new BusinessException(ErrorCode.SYSTEM_ERROR, "删除失败");
        }
        //删除team表中对应的队伍
        boolean result = this.removeById(teamId);
        return result;

    }

    /**
     * 查询我创建的队伍
     * @param teamQueryRequest
     * @return
     */
    @Override
    public List<TeamUserVO> listMyCreateTeam(TeamQueryRequest teamQueryRequest) {
        Long userId = teamQueryRequest.getUserId();
        if(userId == null){
            throw new BusinessException(ErrorCode.NOT_LOGIN_ERROR);
        }
        //查询出当前登录用户所有的队伍
        QueryWrapper<Team> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("userId",userId);
        List<Team> teamList = this.list(queryWrapper);

        List<TeamUserVO> teamUserVOList = new ArrayList<>();
        for (Team team : teamList) {
            TeamUserVO teamUserVO = new TeamUserVO();
            BeanUtils.copyProperties(team,teamUserVO);
            teamUserVOList.add(teamUserVO);
        }
        return teamUserVOList;
    }

    @Override
    public String uploadUserAvatar(FileUploadBizEnum fileUploadBizEnum, MultipartFile multipartFile) {
        // 文件目录：根据业务、用户来划分
        String uuid = RandomStringUtils.randomAlphanumeric(8);
        String filename = uuid + "-" + multipartFile.getOriginalFilename();
        String filepath = String.format("/%s/%s", fileUploadBizEnum.getValue(), filename);
        File file = null;
        try {
            // 上传文件
            file = File.createTempFile(filepath, null);
            multipartFile.transferTo(file);
            cosManager.putObject(filepath, file);
            // 返回可访问地址
            String teamIconPath = FileConstant.COS_HOST + filepath;
            return teamIconPath;
        } catch (Exception e) {
            log.error("file upload error, filepath = " + filepath, e);
            throw new BusinessException(ErrorCode.SYSTEM_ERROR, "上传失败");
        } finally {
            if (file != null) {
                // 删除临时文件
                boolean delete = file.delete();
                if (!delete) {
                    log.error("file delete error, filepath = {}", filepath);
                }
            }
        }
    }

    /**
     * 根据前端传来的teamId查数据库获取team信息
     *
     * @param teamId
     * @return
     */
    private Team getTeamById(Long teamId) {
        if (teamId == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        Team team = this.getById(teamId);//所要删除的队伍信息
        if (team == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "队伍不存在");
        }
        return team;
    }

}




